package com.idroc.endpoint.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import com.idroc.endpoint.Serializer;

public class PreferencesSerializer implements Serializer {
	
	private static final int pieceLength = ((3*Preferences.MAX_VALUE_LENGTH)/4);

	private static Preferences root = Preferences.userNodeForPackage(PreferencesSerializer.class);
	
	private Preferences pref = null;
	
	/**
	 * 创建使用特定Preferences的Serializer
	 * @param id -- PreferencesSerializer标记
	 * @return PreferencesSerializer实例
	 * @memo: id用来区别一个特定的Preferences
	 */
	public static PreferencesSerializer newInstance(String id) {
		
		if (null != id && !"".equals(id)) {
			Preferences subPref = root.node(id);
			return new PreferencesSerializer(subPref);
		} else {
			return new PreferencesSerializer(root);
		}
	}
		
	/**
	 * 将对象转化成字节数组
	 * @param o -- 对象
	 * @return -- 字节数组
	 * @throws -- IOException
	 */
	private static byte[] object2Bytes(Serializable o) throws IOException {
		
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream( baos );
		oos.writeObject( o );
		return baos.toByteArray();
	}

	/**
	 * 将数组分片
	 * @param raw -- 原数组
	 * @return -- 分片后的二维数组
	 */
	private static byte[][] breakIntoPieces( byte raw[] ) {
		
	    int numPieces = (raw.length + pieceLength - 1) / pieceLength;
	    byte pieces[][] = new byte[numPieces][];
	    for (int i=0; i<numPieces; ++i) {
	      int startByte = i * pieceLength;
	      int endByte = startByte + pieceLength;
	      if (endByte > raw.length) endByte = raw.length;
	      int length = endByte - startByte;
	      pieces[i] = new byte[length];
	      System.arraycopy( raw, startByte, pieces[i], 0, length );
	    }
	    return pieces;
	}

	/**
	 * 将分片后的字节数组写入Preferences
	 * @param prefs -- Preferences
	 * @param key -- 标记的key
	 * @param pieces -- 分片后的字节数组
	 * @throws BackingStoreException
	 */
	private static void writePieces(Preferences prefs, String key, byte pieces[][] ) throws BackingStoreException {
	   
		Preferences node = prefs.node( key );
	    node.clear();
	    for (int i=0; i<pieces.length; ++i) {
	    	node.putByteArray("" + i, pieces[i]);
	    }
	}

	/**
	 * 读取保存在Preferences中的分片后的字节数组
	 * @param prefs -- Preferences
	 * @param key -- 标记key
	 * @return -- 分片后的字节数组
	 * @throws BackingStoreException
	 */
	private static byte[][] readPieces( Preferences prefs, String key ) throws BackingStoreException {
	    Preferences node = prefs.node( key );
	    String keys[] = node.keys();
	    int numPieces = keys.length;
	    byte pieces[][] = new byte[numPieces][];
	    for (int i=0; i<numPieces; ++i) {
	      pieces[i] = node.getByteArray( ""+i, null );
	    }
	    return pieces;
	}

	/**
	 * 将分片的字节数组合并
	 * @param pieces -- 分片后的字节数组
	 * @return -- 合并后的字节数组
	 */
	private static byte[] combinePieces( byte pieces[][] ) {
		
	    int length = 0;
	    for (int i=0; i<pieces.length; ++i) {
	      length += pieces[i].length;
	    }
	    byte raw[] = new byte[length];
	    int cursor = 0;
	    for (int i=0; i<pieces.length; ++i) {
	      System.arraycopy( pieces[i], 0, raw, cursor, pieces[i].length );
	      cursor += pieces[i].length;
	    }
	    return raw;
	}

	/**
	 * 将字节数组中的数据转换成对象
	 * @param raw -- 持久化对象的字节数据
	 * @return -- 对象
	 * @throws IOException
	 * @throws ClassNotFoundException
	 */
	private  static Object bytes2Object( byte raw[] ) throws IOException, ClassNotFoundException {
	   
		try {
			ByteArrayInputStream bais = new ByteArrayInputStream( raw );
		    ObjectInputStream ois = new ObjectInputStream( bais );
		    Object o = ois.readObject();
		    return o;
		} catch (Exception ex) {
			return null;
		}
	}

	/**
	 * 保存对象到Preferences
	 * @param prefs -- Preferences
	 * @param key -- 标记key
	 * @param o -- 保存的对象
	 * @throws IOException
	 * @throws BackingStoreException
	 * @throws ClassNotFoundException
	 */
	private static void putObject( Preferences prefs, String key, Serializable o )
	      throws IOException, BackingStoreException, ClassNotFoundException {
		
	    byte raw[] = object2Bytes( o );
	    byte pieces[][] = breakIntoPieces( raw );
	    writePieces( prefs, key, pieces );
	}

	/**
	 * 获取保存在Preferences的对象
	 * @param prefs -- Preferences
	 * @param key -- 标记key
	 * @return -- 对象
	 * @throws IOException
	 * @throws BackingStoreException
	 * @throws ClassNotFoundException
	 */
	private static Object getObject( Preferences prefs, String key )
	      throws IOException, BackingStoreException, ClassNotFoundException {
		
		byte pieces[][] = readPieces( prefs, key );
		byte raw[] = combinePieces( pieces );
		Object o = bytes2Object( raw );
		return o;
	}
	
	private PreferencesSerializer(Preferences pref) {
		this.pref = pref;
	}
	
	/*
	 * 加载对象
	 * @see com.idroc.endpoint.Serializer#load(java.lang.String)
	 */
	public Object load(String prop) {  
		
		try {
			return getObject(pref, prop);
		} catch (Exception ex) {
			throw new RuntimeException("加载属性值异常", ex);
		} 
	}

	/*
	 * 保存对象
	 * @see com.idroc.endpoint.Serializer#save(java.lang.String, java.io.Serializable)
	 */
	public boolean save(String prop, Serializable value) { 
		
		try {
			putObject(pref, prop, value);
			return true;
		} catch (Exception ex) {
			throw new RuntimeException("加载属性值异常", ex);
		} 
	}

	public void close() {
		
		// do nothing;
	}

	public void open() {
		// do nothing;
	} 
	
	public void clean() {
		try {
			this.pref.clear();
		} catch (BackingStoreException ex) { 
			throw new RuntimeException(ex);
		}
	}

	/*
	 * 
	 * @see com.idroc.endpoint.Serializer#cleanup(java.lang.String)
	 */
	public void cleanup(String prop) { 
		try {
			this.pref.remove(prop);
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}
}
